/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.template;

import axil.api.*;
import axil.engine.AxilContext;
import axil.framework.localization.TranslationValue;
import axil.stdlib.core.type.Atom;
import axil.stdlib.text.type.Str;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;

import static axil.framework.Functions.abort;
import static axil.framework.Functions.persona;


/**
 * An Axil template. A template is a string that contains variable references
 * (and even Axil code) that can be executed at runtime and resolved into text.
 * This is also a first-class type in Axil.
 */
public class Template extends Atom implements TranslationValue {
	public static final AxilType type = new Template_Type();
	private static final Context empty = AxilContext.empty;
	public static final Template blank = new Template(new Instruction[0]);

	private final Instruction[] code;


	Template(Instruction[] code) {
		this.code = code;
	}


	/**
	 * Get the type metadata for this object. The type metadata contains
	 * information about the type itself, as an object.
	 */
	public AxilType type() {
		return type;
	}


	/**
	 * Provide a description of the type of this object. The string returned is
	 * the Axil type name, not the Java name for any implementation. For example,
	 * "integer", not "int" or "Integer".
	 *
	 * @return
	 * 	Returns the name of the Axil type.
	 */
	public String identity() {
		return "template";
	}


	public static Template compile(String reference, String source) {
		return new TemplateCompiler(reference,
		                            new StringReader(source)).compile();
	}


	public static Template compile(String reference, Reader source) {
		return new TemplateCompiler(reference, source).compile();
	}


	/**
	 * Returns a hash code value for the object. All Axil values must provide a
	 * meaningful implementation of this function to enable themselves to be
	 * placed in a map or set.
	 */
	protected int hash() {
		return System.identityHashCode(this);
	}


	/**
	 * Tell if this object is equivalent to the given object. The object given
	 * is never null. The object given may be of any type of value object.  If
	 * the given object is not a suitable type for comparison, a
	 * ClassCastException may be thrown.
	 */
	public boolean equalTo(AxilObject other) {
		if (! (other instanceof Template)) {
			return false;
		}
		Template t = (Template)other;
		final int size = code.length;
		if (size != t.code.length) {
			return false;
		}
		for (int i = 0; i < size; i++) {
			if (! code[i].equals(t.code[i])) {
				return false;
			}
		}
		return true;
	}


	/**
	 * Compares this object with the specified object for order. If the given
	 * object is not a suitable type for comparison, a ClassCastException may
	 * be thrown.
	 *
	 * @param object
	 * 	The object to compare against. The given object cannot be null but may
	 * 	be any Axil object.
	 *
	 * @return
	 * 	Returns a negative integer, zero, or a positive integer as this object
	 * 	is less than, equal to, or greater than the specified object.
	 */
	public int comparedTo(AxilObject object) {
		throw abort("There is no meaningful implementation to this.");
	}


	public Str optimize() {
		if (code.length == 1) {
			if (code[0] instanceof StaticText) {
				return Str.from(((StaticText)code[0]).text());
			}
		}
		return null;
	}


	/**
	 * Return the text value appropriate for the given size. The size is used
	 * to select an appropriate translation when plurals are involved.
	 */
	public String text(int size) {
		return format(persona(), size, empty);
	}


	/**
	 * Return the translated, localized and formatted contents of this
	 * translation. The size is used to select an appropriate translation when
	 * plurals are involved.
	 */
	public String format(Persona persona, int size, Context values) {
		StringBuilder b = new StringBuilder();
		for (Instruction i : code) {
			b.append(i.execute(persona, values));
		}
		return b.toString();
	}


	/**
	 * Format this object in a locale-friendly manner. The string returned is
	 * a localized representation of this object using the locale and timezone
	 * associated with this persona. The formatting given is never null and
	 * matches formatting suitable for this type.
	 */
	public String format(Persona persona, Formatting formatting) {
		return text(1);
	}


	/**
	 * Write the translated, localized and formatted contents of this
	 * translation into the given output stream. The size is used to select an
	 * appropriate translation when plurals are involved.
	 */
	public void write(Persona persona, int size, Writer output, Context context)
	throws IOException {
		for (Instruction i : code) {
			output.write(i.execute(persona, context));
		}
	}


	/**
	 * Return a string representation of this object. The string returned is
	 * never null.
	 */
	public String toString() {
		return text(0);
	}
}
